#include <suite.h>

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <sys/time.h>
#include <unistd.h>

#include <errors.h>
#include <eval.h>
#include <fen.h>
#include <game.h>
#include <interface.h>
#include <movegen.h>
#include <perft.h>
#include <position.h>
#include <san.h>
#include <search.h>
#include <utils.h>


typedef struct suite_element_t
{
  position_t pos;
  const char** ops;
  int lineno;
} suite_element_t;


static int load_suite(const char* filename, suite_element_t** suite)
{
  FILE* fp = fopen(filename, "rt");
  if (fp == 0) {
    fprintf(stderr, "Error (internal): cannot open '%s'\n", filename);
    return -1;
  }

  int n = 0;
  int capacity = 16;
  suite_element_t* res;
  res = (suite_element_t*)malloc(sizeof(suite_element_t) * capacity);
  if (res == 0) {
    fprintf(stderr, "Error (internal): memory allocation failure\n");
    return -1;
  }

  position_t pos;
  size_t len;
  size_t linebuflen = 0;
  char* line = 0;
  int lineno = 0;
  while ((len = getline(&line, &linebuflen, fp)) != -1) {
    ++lineno;
    char* epd = trim_string(line);
    if (epd[0] == '\0' || epd[0] == '#')
      continue;

    const char** opts = decode_epd(epd, &pos);
    if (!opts) {
      const char* fmt = "Error (epd): ill formed EPD line (%s:%d)\n";
      fprintf(stderr, fmt, filename, lineno);
      continue;
    }

    if (n == capacity) {
      capacity *= 2;
      void* mem = realloc(res, sizeof(suite_element_t) * capacity);
      if (mem == 0) {
	fprintf(stderr, "Error (internal): memory allocation failure\n");
	free(res);
	for (int i = 0; i < n; i++)
	  free(res[i].ops);
	free(line);
	return -1;
      }
      res = (suite_element_t*)mem;
    }

    res[n].pos = pos;
    res[n].ops = opts;
    res[n].lineno = lineno;
    ++n;
  }

  free(line);
  fclose(fp);
  *suite = res;
  return n;
}


void perftsuite(const char* filename)
{
  suite_element_t* suite;
  int n = load_suite(filename, &suite);
  if (n < 0)
    return;

  int ok = 0;
  double time = 0;

  game_t game;
  game_init(&game);

  int positions = 0;
  for (int i = 0; i < n; i++) {
#if HAVE_ISATTY
    if (isatty(1)) {
      printf("%3d%%\r", (100 * i) / n);
      fflush(stdout);
    }
#endif
    if (!game_set_start_position(&game, &(suite[i].pos))) {
      printf("Error (epd): %s (%s:%d)\n",
	     error_string(error_last()),
	     filename, suite[i].lineno);
      continue;
    }

    int tested = 0;
    int passed = 0;
    for (const char** o = suite[i].ops; *o; o += 2) {
      if ((*o)[0] != 'D')
	continue;
      char* endp;
      long depth = strtol((*o) + 1, &endp, 10);
      if (*endp != '\0' || depth < 1)
	continue;

      if (*(o + 1) == 0) {
	const char* fmt = "Error (epd): no perft argument (%s:%d)\n";
	printf(fmt, filename, suite[i].lineno);
	continue;
      }

      int64_t temp = strtoll(*(o + 1), &endp, 10);
      if (*endp != '\0' || temp < 0) {
	const char* fmt = "Error (epd): invalid perft argument (%s:%d)\n";
	fprintf(stderr, fmt, filename, suite[i].lineno);
	continue;
      }
      uint64_t expected = temp;

      ++tested;
      struct timeval start;
      gettimeofday(&start, 0);
      uint64_t nodes = perft_position(&game, depth);
      struct timeval end;
      gettimeofday(&end, 0);
      time += end.tv_sec - start.tv_sec;
      time += (end.tv_usec - start.tv_usec) * 1e-6;

      if (nodes != expected) {
      	const char* fmt = "%s:%d: nodes mismatch at depth %d";
	printf(fmt, filename, suite[i].lineno, depth);
	const char* fmt2 = " (expected %" PRIu64 " nodes, got %" PRIu64 ")\n";
	printf(fmt2, expected, nodes);
	break;
      }
      ++passed;
    }
    if (tested > 0) {
      ++positions;
      if (tested == passed)
	++ok;
    }
  }
  game_cleanup(&game);

  for (int i = 0; i < n; i++)
    free(suite[i].ops);
  free(suite);

  if (ok != positions)
    printf("--  \n");
  printf("%d/%d positions passed ", ok, positions);
  printf("(");
  print_time(stdout, time);
  printf(").\n");
}


void searchsuite(const char* filename,
		 const struct interface_t* iface)
{
  char buf[MAX_SAN_LEN];
  suite_element_t* suite;
  int n = load_suite(filename, &suite);
  if (n < 0)
    return;

  double time = 0;
  int tested = 0;
  int passed = 0;

  interface_t interface;
  interface_init(&interface);

  for (int i = 0; i < n; i++) {
    interface_copy(iface, &interface);
    if (!game_set_start_position(&(interface.game), &(suite[i].pos))) {
      printf("Error (epd): %s (%s:%d)\n",
	     error_string(error_last()),
	     filename, suite[i].lineno);
      continue;
    }

    const char** o;
    const char** best_moves = 0;
    long dm = 0;
    
    for (o = suite[i].ops; *o; o += 2) {
      if (!strcmp(*o, "bm")) {
	if (best_moves == 0) {
	  best_moves = decode_epd_args(*(o + 1));
	} else {
	  const char* fmt = "Warning (epd): multiple bm opcode (%s:%d)\n";
	  fprintf(stderr, fmt, filename, suite[i].lineno);
	}
      }
      if (!strcmp(*o, "dm")) {
	if (dm == 0) {
	  char* endp;
	  dm = strtol(*(o + 1), &endp, 10);
	  if (*endp != '\0' || dm < 1) {
	    const char* fmt = "Warning (epd): invalid dm opcode '%s' (%s:%d)\n";
	    fprintf(stderr, fmt, *(o + 1), filename, suite[i].lineno);
	    dm = -1;
	  }
	} else {
	  const char* fmt = "Warning (epd): multiple dm opcode (%s:%d)\n";
	  fprintf(stderr, fmt, filename, suite[i].lineno);
	}
      }      
    }

    if (best_moves == 0) {
      const char* fmt = "Warning (epd): missing or invalid bm argument (%s:%d)\n";
      fprintf(stderr, fmt, filename, suite[i].lineno);
      continue;
    }
    if (*best_moves == 0) {
      free(best_moves);
      const char* fmt = "Warning (epd): empty bm argument (%s:%d)\n";
      fprintf(stderr, fmt, filename, suite[i].lineno);
      continue;
    }

    printf("Searching position %d of %d\n", i + 1, n);
    if (dm > 0) {
      interface.depth_limit = 2 * dm - 1;
      printf("Mate in %ld\n", dm);
    }
    struct timeval start;
    gettimeofday(&start, 0);
    move_t m = search_start(&interface);
    struct timeval end;
    gettimeofday(&end, 0);
    game_undo_move(&(interface.game));
    time += end.tv_sec - start.tv_sec;
    time += (end.tv_usec - start.tv_usec) * 1e-6;

    int ok = 0;
    int error = 0;
    for (const char** b = best_moves; *b; b++) {
      move_t best;
      if (!move_from_san_string(*b, &(suite[i].pos), &best)) {
	const char* fmt = ("Error (epd): best move '%s' "
			   "is illegal (%s:%d)\n");
	fprintf(stderr, fmt, *b, filename, suite[i].lineno);
	error = 1;
	break;
      }
      if (same_move(m, best)) {
	ok = 1;
	break;
      }
    }
    if (!error) {
      ++tested;
      if (ok) {
	++passed;
	printf("Ok\n");
      } else {
	const char* fmt = "%s:%d: best move mismatch (expected";
	printf(fmt, filename, suite[i].lineno);
	for (const char** b = best_moves; *b; b++)
	  printf(" %s", *b);
	printf(", got %s)\n", move_to_san_string(m, &(suite[i].pos), buf));
      }
    }
    free(best_moves);
  }
  interface_cleanup(&interface);

  for (int i = 0; i < n; i++)
    free(suite[i].ops);
  free(suite);

  if (tested != passed)
    printf("--  \n");
  printf("%d/%d positions passed ", passed, tested);
  printf("(");
  print_time(stdout, time);
  printf(").\n");
}


void evalsuite(const char* filename)
{
  eval_set_random(0);
  suite_element_t* suite;
  int n = load_suite(filename, &suite);
  if (n < 0)
    return;

  double time = 0;
  int notok = 0;
  int reversed = 0;

  for (int i = 0; i < n; i++) {
    position_t pos = suite[i].pos;
    evaluation_t ev;
    int sign = (position_get_stm(&pos) == WHITE ? 1 : -1);
    eval_detail(&pos, &ev);
    printf("Position %3d", i + 1);
    
    if (movegen_detect_check(&pos) == 0) {
      ++reversed;
      position_reverse(&pos, &pos);
      evaluation_t rev;
      eval_detail(&pos, &rev);
      int inv = -sign;

      printf(" (reversed)\n");
      printf("Phase    %d\n", ev.phase);
      printf("Material (mid) %+6d  (%+6d)\n", sign * ev.material[0],
	     inv * rev.material[0]);
      printf("Material (end) %+6d  (%+6d)\n", sign * ev.material[1],
	     inv * rev.material[1]);
      printf("Pawns    (mid) %+6d  (%+6d)\n", sign * ev.pawns[0],
	     inv * rev.pawns[0]);
      printf("Pawns    (end) %+6d  (%+6d)\n", sign * ev.pawns[1],
	     inv * rev.pawns[1]);
      printf("Knights  (mid) %+6d  (%+6d)\n", sign * ev.knights[0],
	     inv * rev.knights[0]);
      printf("Knights  (end) %+6d  (%+6d)\n", sign * ev.knights[1],
	     inv * rev.knights[1]);
      printf("Bishops  (mid) %+6d  (%+6d)\n", sign * ev.bishops[0],
	     inv * rev.bishops[0]);
      printf("Bishops  (end) %+6d  (%+6d)\n", sign * ev.bishops[1],
	     inv * rev.bishops[1]);
      printf("Rooks    (mid) %+6d  (%+6d)\n", sign * ev.rooks[0],
	     inv * rev.rooks[0]);
      printf("Rooks    (end) %+6d  (%+6d)\n", sign * ev.rooks[1],
	     inv * rev.rooks[1]);
      printf("Queens   (mid) %+6d  (%+6d)\n", sign * ev.queens[0],
	     inv * rev.queens[0]);
      printf("Queens   (end) %+6d  (%+6d)\n", sign * ev.queens[1],
	     inv * rev.queens[1]);
      printf("Kings    (mid) %+6d  (%+6d)\n", sign * ev.kings[0],
	     inv * rev.kings[0]);
      printf("Kings    (end) %+6d  (%+6d)\n", sign * ev.kings[1],
	     inv * rev.kings[1]);
      printf("Midgame total  %+6d  (%+6d)\n", sign * ev.phase_total[0],
	     inv * rev.phase_total[0]);
      printf("Endgame total  %+6d  (%+6d)\n", sign * ev.phase_total[1],
	     inv * rev.phase_total[1]);
      printf("TOTAL    %+6d  (%+6d)\n", sign * ev.total,
	     inv * rev.total);

      if (ev.phase != rev.phase ||
	  ev.material[0] != rev.material[0] ||
	  ev.material[1] != rev.material[1] ||
	  ev.pawns[0] != rev.pawns[0] ||
	  ev.pawns[1] != rev.pawns[1] ||
	  ev.knights[0] != rev.knights[0] ||
	  ev.knights[1] != rev.knights[1] ||
	  ev.bishops[0] != rev.bishops[0] ||
	  ev.bishops[1] != rev.bishops[1] ||
	  ev.rooks[0] != rev.rooks[0] ||
	  ev.rooks[1] != rev.rooks[1] ||
	  ev.queens[0] != rev.queens[0] ||
	  ev.queens[1] != rev.queens[1] ||
	  ev.kings[0] != rev.kings[0] ||
	  ev.kings[1] != rev.kings[1] ||
	  ev.phase_total[0] != rev.phase_total[0] ||
	  ev.phase_total[1] != rev.phase_total[1] ||
	  ev.total != rev.total) {
	printf("WARNING: mirrored scores do not agree (%s:%d).\n",
	       filename, suite[i].lineno);
	++notok;
      }
    } else {
      printf(" (in check)\n");
      printf("Phase    %d\n", ev.phase);
      printf("Material (mid) %+6d\n", sign * ev.material[0]);
      printf("Material (end) %+6d\n", sign * ev.material[1]);
      printf("Pawns    (mid) %+6d\n", sign * ev.pawns[0]);
      printf("Pawns    (end) %+6d\n", sign * ev.pawns[1]);
      printf("Knights  (mid) %+6d\n", sign * ev.knights[0]);
      printf("Knights  (end) %+6d\n", sign * ev.knights[1]);
      printf("Bishops  (mid) %+6d\n", sign * ev.bishops[0]);
      printf("Bishops  (end) %+6d\n", sign * ev.bishops[1]);
      printf("Rooks    (mid) %+6d\n", sign * ev.rooks[0]);
      printf("Rooks    (end) %+6d\n", sign * ev.rooks[1]);
      printf("Queens   (mid) %+6d\n", sign * ev.queens[0]);
      printf("Queens   (end) %+6d\n", sign * ev.queens[1]);
      printf("Kings    (mid) %+6d\n", sign * ev.kings[0]);
      printf("Kings    (end) %+6d\n", sign * ev.kings[1]);
      printf("Midgame total  %+6d\n", sign * ev.phase_total[0]);
      printf("Endgame total  %+6d\n", sign * ev.phase_total[1]);
      printf("TOTAL    %+6d\n", sign * ev.total);
    }
    printf("\n");
  }
  
  for (int i = 0; i < n; i++)
    free(suite[i].ops);
  free(suite);

  printf("--\n%d positions evaluated ", n);
  printf("(");
  print_time(stdout, time);
  printf(").\n");
  if (notok > 0) {
    printf("Warning: %d scores of %d were not correctly "
	   "inverted.\n", notok, reversed);
  }
}
